package cool.doudou.doudada.cipher.algorithm.util;

import cool.doudou.doudada.cipher.algorithm.enums.Algorithm;
import org.bouncycastle.util.encoders.Hex;
import org.springframework.util.ObjectUtils;

import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

/**
 * 非对称AES
 *
 * @author jiangcs
 * @since 2022/07/04
 */
public class AesUtil {
    private static final String transformationGCM = "AES/GCM/NoPadding";
    private static final String transformationCBC = "AES/CBC/NoPadding";

    /**
     * 加密
     * <p>
     * GCM+NoPadding+Base64
     *
     * @param encryptKey 加密Key
     * @param iv         初始化向量
     * @param plaintext  明文
     * @param salt       盐
     * @return Base64字符串
     * @throws Exception 加密异常
     */
    public static String encryptGCM(String encryptKey, String iv, String plaintext, String salt) throws Exception {
        SecretKeySpec secretKeySpec = new SecretKeySpec(encryptKey.getBytes(StandardCharsets.UTF_8), Algorithm.AES.code());
        GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, iv.getBytes(StandardCharsets.UTF_8));

        Cipher cipher = Cipher.getInstance(transformationGCM);
        cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, gcmParameterSpec);
        if (!ObjectUtils.isEmpty(salt)) {
            cipher.updateAAD(salt.getBytes(StandardCharsets.UTF_8));
        }
        byte[] bytes = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(bytes);
    }

    /**
     * 解密
     * <p>
     * GCM+NoPadding+Base64
     *
     * @param decryptKey 解密Key
     * @param iv         初始化向量
     * @param ciphertext Base64密文
     * @param salt       盐
     * @return 字符串
     * @throws Exception 解密异常
     */
    public static String decryptGCM(String decryptKey, String iv, String ciphertext, String salt) throws Exception {
        SecretKeySpec secretKeySpec = new SecretKeySpec(decryptKey.getBytes(StandardCharsets.UTF_8), Algorithm.AES.code());
        GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, iv.getBytes(StandardCharsets.UTF_8));

        Cipher cipher = Cipher.getInstance(transformationGCM);
        cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, gcmParameterSpec);
        if (!ObjectUtils.isEmpty(salt)) {
            cipher.updateAAD(salt.getBytes(StandardCharsets.UTF_8));
        }
        byte[] bytes = cipher.doFinal(Base64.getDecoder().decode(ciphertext));
        return new String(bytes, StandardCharsets.UTF_8);
    }

    /**
     * 加密
     * <p>
     * CBC+NoPadding+Hex
     *
     * @param encryptKey 加密Key
     * @param iv         初始化向量
     * @param plaintext  明文
     * @param salt       盐
     * @return Hex字符串
     * @throws Exception 加密异常
     */
    public static String encryptCBC4Hex(String encryptKey, String iv, String plaintext, String salt) throws Exception {
        SecretKeySpec secretKeySpec = new SecretKeySpec(encryptKey.getBytes(StandardCharsets.UTF_8), Algorithm.AES.code());
        IvParameterSpec ivParameterSpec = new IvParameterSpec(iv.getBytes());
        Cipher cipher = Cipher.getInstance(transformationCBC);
        cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
        if (!ObjectUtils.isEmpty(salt)) {
            cipher.updateAAD(salt.getBytes(StandardCharsets.UTF_8));
        }
        // 加密或者解密内容字节长度必须为16的整数倍，否则会报错
        StringBuilder sbPlaintext = new StringBuilder(plaintext);
        while (sbPlaintext.toString().getBytes(StandardCharsets.UTF_8).length % 16 != 0) {
            sbPlaintext.append(" ");
        }
        plaintext = sbPlaintext.toString();
        byte[] bytes = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
        return Hex.toHexString(bytes);
    }

    /**
     * 解密
     * <p>
     * CBC+NoPadding+Hex
     *
     * @param decryptKey 解密Key
     * @param iv         初始化向量
     * @param ciphertext Hex密文
     * @param salt       盐
     * @return 字符串
     * @throws Exception 解密异常
     */
    public static String decryptCBC4Hex(String decryptKey, String iv, String ciphertext, String salt) throws Exception {
        SecretKeySpec secretKeySpec = new SecretKeySpec(decryptKey.getBytes(StandardCharsets.US_ASCII), Algorithm.AES.code());
        IvParameterSpec ivParameterSpec = new IvParameterSpec(iv.getBytes());
        Cipher cipher = Cipher.getInstance(transformationCBC);
        cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
        if (!ObjectUtils.isEmpty(salt)) {
            cipher.updateAAD(salt.getBytes(StandardCharsets.UTF_8));
        }
        byte[] bytes = cipher.doFinal(Hex.decode(ciphertext));
        return new String(bytes, StandardCharsets.UTF_8);
    }
}
